﻿using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Amazon.Glacier;
using Amazon.Glacier.Model;

namespace AmazonGlacierGUI.Core.Helpers.AmazonGlacier
{
    //23-Aug-2012 Priyan R
    public class AmazonGlacierDownloadHelper : AmazonGlacierHelperBase
    {
        private CancellationTokenSource _cancelTokenSource;

        public event EventHandler<ProgressEventArgs> OnProgress;
        public event EventHandler<ErrorEventArgs> OnError;
        public event EventHandler<CompletedEventArgs> OnComplete;
        public event EventHandler OnCancelled;
        public AmazonGlacierDownloadHelper()
        {

        }
        public string InitiateDownloadRequest(string archiveId)
        {

            InitClient();
            var initJobRequest = new InitiateJobRequest();
            initJobRequest.VaultName = VaultName;
            initJobRequest.JobParameters = new JobParameters()
            {
                Type = "archive-retrieval",
                ArchiveId = archiveId,
            };
            InitiateJobResponse initJobResponse = _client.InitiateJob(initJobRequest);
            return initJobResponse.InitiateJobResult.JobId;
        }
        public JobRequestStatusEnum GetJobStatus(string jobId)
        {

            InitClient();
            var resp = GetDescribeJobResponse(jobId);            
            if(resp.DescribeJobResult.Completed)
                return JobRequestStatusEnum.Completed;
            return JobRequestStatusEnum.InProgress;            
        }
        
        public void Cancel()
        {
            _cancelTokenSource.Cancel();
            
        }
        public void DownloadArchive(string jobId, string filePath,bool resumeIfExist)
        {
            IsProcessing = true;
            _cancelTokenSource = new CancellationTokenSource();
            InitClient();
            Task.Factory.StartNew(() => DownlodAsync(jobId, filePath,resumeIfExist), _cancelTokenSource.Token);
        }
        void DownlodAsync(string jobId, string filePath, bool resumeIfExist)
        {
            try
            {
                long seekBytes = 0;
                var request = new GetJobOutputRequest()
                {
                    JobId = jobId,
                    VaultName = VaultName
                };
                if (File.Exists(filePath)&&resumeIfExist)
                {
                    seekBytes = new FileInfo(filePath).Length -(1024 * 50);
                    seekBytes = seekBytes < 0 ? 0 : seekBytes;
                    request.SetRange(seekBytes, null);
                }
                var jobDescription = GetDescribeJobResponse(jobId);
                var jobOutPutResponse = _client.GetJobOutput(request);
                var fileSize = jobDescription.DescribeJobResult.ArchiveSizeInBytes;
                var isCancelled = false;
                using (var iStream = jobOutPutResponse.GetJobOutputResult.Body)
                {
                    using (var oStream = File.OpenWrite(filePath))
                    {
                        oStream.Seek(seekBytes, SeekOrigin.Begin);
                        while (true)
                        {
                            if (_cancelTokenSource.IsCancellationRequested)
                            {
                                isCancelled = true;
                                break;                                
                            }
                            var length = 1024 * 22;
                            var by = new byte[length];
                            var readBytes = iStream.Read(by, 0, (int)length);
                            if (readBytes == 0)
                            {
                                if (oStream.Position != fileSize)
                                {
                                    throw new Exception("Error occured while transfering data");
                                }
                                break;
                            }
                            oStream.Write(by, 0, readBytes);
                            RaiseProgress(oStream.Position, fileSize);
                        }
                    }
                }
                if(isCancelled)
                {
                    RaiseCancelled();
                    return;
                }
                using (var stream = File.OpenRead(filePath))
                {
                    if (TreeHashGenerator.CalculateTreeHash(stream) != jobDescription.DescribeJobResult.SHA256TreeHash)
                    {
                        throw new Exception("CRC Failed");
                    }
                    else
                    {
                        var crcok = "asd";
                    }
                }
                IsProcessing = false;
                RaiseComplete();
            }
            catch (Exception ex)
            {
                IsProcessing = false;
                RaiseError(GetExceptionMessage(ex));
            }
            finally
            {
                IsProcessing = false;
            }
        }
        void RaiseProgress(long bytesDownloaded, long totalBytes)
        {
            if (OnProgress == null) return;
            OnProgress(this, new ProgressEventArgs(bytesDownloaded, totalBytes));
        }
        void RaiseComplete()
        {
            if (OnComplete == null) return;
            OnComplete(this, new CompletedEventArgs());
        }
        void RaiseError(string errorMessage)
        {
            if (OnError == null) return;
            OnError(this, new ErrorEventArgs(errorMessage));
        }
        void RaiseCancelled()
        {
            if (OnCancelled == null) return;
            OnCancelled(this,null);
        }
        public class ProgressEventArgs : EventArgs
        {
            public ProgressEventArgs(long downloadedBytes, long totalBytes)
            {
                DownloadedBytes = downloadedBytes;
                TotalBytes = totalBytes;
            }

            public long DownloadedBytes { get; set; }
            public long TotalBytes { get; set; }

        }
        public class ErrorEventArgs : EventArgs
        {
            public ErrorEventArgs(string errorMessage)
            {
                ErrorMessage = errorMessage;
            }

            public string ErrorMessage { get; set; }
        }
        public class CompletedEventArgs : EventArgs
        {
            public CompletedEventArgs()
            {
            }
        }
        public enum JobRequestStatusEnum
        {
            InProgress,
            Completed
        }
    }
}//
